
/**
 *	TrueMusic - simple java mp3 player
 *
 *	TrueMusic is free software; you can redistribute it
 *	and/or modify it under the terms of the GNU General
 *	Public License as published by the Free Software
 *	Foundation; either version 2 of the License or (at
 *	your option) any later version.
 *	
 *	This program is distributed in the hope that it will be
 *	useful but WITHOUT ANY WARRANTY; without even the 
 *	implied warranty of the MERCHANTABILITY or FITNESS FOR
 *	A PARTICULAR PURPOSE. See the GNU General Public
 *	License for more details.
 *	
 *	Copyright (c)2005-2007 Ryan Macnak <rmacnak@gmail.com>
 */

package com.servequake.truemusic;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;
import java.io.*;
import java.awt.dnd.*;
import java.awt.datatransfer.*;

public class TrueMusic extends JFrame implements PlayerListener, KeyListener, ActionListener, DropTargetListener, MouseListener, ItemListener, ChangeListener
{
	final TrueMusic parent = this;

	Playlist p = new Playlist("Current",this);
	JTable t = new JTable(p);
	JList list = new JList(p);

	JButton add = new JButton("+");
	JToggleButton random = new JToggleButton("~");
	JToggleButton repeat = new JToggleButton("\u221e"); //infinity sign

	Vector playlists = new Vector();
	JComboBox playlistsbox;
	
	JLabel title = new JLabel( "Add music to your playlist." , SwingConstants.CENTER);
	JLabel artist = new JLabel("", SwingConstants.CENTER);
	JLabel album = new JLabel("http://truemusic.servequake.com/", SwingConstants.CENTER);

	JButton play = new JButton(">");
	JButton pause = new JButton("||");
	JButton stop = new JButton("X");
	JButton next = new JButton(">>");
	JButton previous = new JButton("<<");

	JSlider seek = new JSlider(0,1);
	JSlider volume = new JSlider(JSlider.VERTICAL,-80,0,-20);
	
	JLabel npTitle = new JLabel("", SwingConstants.CENTER);
	JLabel npArtist = new JLabel("", SwingConstants.CENTER);
	JLabel npAlbum = new JLabel("", SwingConstants.CENTER);
	JLabel npTrack = new JLabel("", SwingConstants.CENTER);
	JLabel npGenre = new JLabel("", SwingConstants.CENTER);
	JLabel npFormat = new JLabel("", SwingConstants.CENTER);
	
	private TrueMusic()
	{
		super("TrueMusic");

		playlists.add(p);
		playlists.add( "[Create new playlist]" );
		playlists.add( "[Remove current playlist]" );		
		playlistsbox = new JComboBox( playlists );
		
		title.setFont( title.getFont().deriveFont(Font.BOLD) );
		
		play.addActionListener(this);
		pause.addActionListener(this);
		stop.addActionListener(this);
		next.addActionListener(this);
		previous.addActionListener(this);
		seek.addChangeListener(this);
		volume.addChangeListener(this);
		playlistsbox.addItemListener(this);
		add.addActionListener(this);
		random.addActionListener(this);
		repeat.addActionListener(this);
		t.addMouseListener(this); //!
		t.addKeyListener(this); //!
		list.addMouseListener(this);
		list.addKeyListener(this);
	
		t.setDragEnabled(true); //!
		new DropTarget(t,this); //!
		list.setDragEnabled(true);
		new DropTarget(t,this);
		
		t.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); //!
		t.setShowGrid(false); //!
		list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		
		previous.setToolTipText( "Previous Track" );
		pause.setToolTipText( "Pause" );
		play.setToolTipText( "Play" );
		stop.setToolTipText( "Stop" );
		next.setToolTipText( "Next Track" );
		seek.setToolTipText( "Seek" );
		volume.setToolTipText( "Volume" );
		title.setToolTipText( "Track Title" );
		artist.setToolTipText( "Artist" );
		album.setToolTipText( "Album" );
		list.setToolTipText( "Current Playlist" );
		playlistsbox.setToolTipText(  "Playlists" );
		add.setToolTipText( "Add tracks to your playlist" );
		random.setToolTipText( "Random" );
		repeat.setToolTipText( "Repeat" );

		
		JPanel info = new JPanel(new GridLayout(3,1));
		info.add(title);
		info.add(artist);
		info.add(album);

		JPanel row = new JPanel(new GridLayout(1,5));
		row.add(previous);
		row.add(pause);
		row.add(play);
		row.add(stop);
		row.add(next);

		JPanel cb = new JPanel(new GridLayout(2,1));
		cb.add(row);
		cb.add(seek);

		JPanel left = new JPanel(new BorderLayout());
		left.add(info);
		left.add(cb, BorderLayout.SOUTH);

		JPanel cp = new JPanel(new BorderLayout());
		cp.setBorder( BorderFactory.createTitledBorder("") );
		cp.add( left );
		cp.add( volume, BorderLayout.EAST );
		cp.setPreferredSize( new Dimension(285,120) );

		JPanel pb = new JPanel(new GridLayout(1,3));
		pb.add(add);
		pb.add(random);
		pb.add(repeat);
		
		JPanel pt = new JPanel(new BorderLayout());
		pt.add( playlistsbox );
		pt.add( pb, BorderLayout.EAST );

		JPanel pp = new JPanel(new BorderLayout());
		pp.setBorder( BorderFactory.createTitledBorder("") );
		pp.add( new JScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER) );
		pp.add( pt, BorderLayout.NORTH );
		
		JPanel waytop = new JPanel(new BorderLayout());
		waytop.add( cp, BorderLayout.WEST );
		waytop.add( pp );
		waytop.setPreferredSize( new Dimension(600,120) );
		
		JTabbedPane tabs = new JTabbedPane();
		tabs.setBorder( BorderFactory.createTitledBorder("") );
		
		JPanel nowPlaying = new JPanel(new GridLayout(6,2));
		nowPlaying.add( new JLabel("Title",SwingConstants.CENTER) );
		nowPlaying.add( npTitle );
		nowPlaying.add( new JLabel("Artist",SwingConstants.CENTER) );
		nowPlaying.add( npArtist );
		nowPlaying.add( new JLabel("Album",SwingConstants.CENTER) );
		nowPlaying.add( npAlbum );
		nowPlaying.add( new JLabel("Track",SwingConstants.CENTER) );
		nowPlaying.add( npTrack );
		nowPlaying.add( new JLabel("Genre",SwingConstants.CENTER) );
		nowPlaying.add( npGenre );
		nowPlaying.add( new JLabel("Format",SwingConstants.CENTER) );
		nowPlaying.add( npFormat );
		tabs.add("Now Playing", nowPlaying);
		
		JPanel library = new JPanel(new BorderLayout());
		library.add(new JLabel("For the time being, the \"library\" just shows the current playlist."),BorderLayout.NORTH);
		library.add(new JScrollPane(t));
		tabs.add("Library", library);
		
		//tabs.add("Import", new JLabel("Import"));
		//tabs.add("Export", new JLabel("Export"));
		//tabs.add("Convert", new JLabel("Convert"));	

		JPanel options = new JPanel(new GridLayout(9,1));
		options.add(new JLabel("TrueMusic 20070918"));
		options.add(new JLabel("http://truemusic.servequake.com/"));
		options.add(new JLabel("AU, AIF: no seeking, no volume"));
		options.add(new JLabel("WAV: seeking, volume"));
		options.add(new JLabel("MP3: seeking, volume, tag editing"));
		options.add(new JLabel("Ogg Vorbis: seeking, volume"));
		options.add(new JLabel("Ogg FLAC: seeking, volume"));
		options.add(new JLabel("Native FLAC: no seeking, volume"));
		options.add(new JLabel("Speex: try at your own risk"));
		tabs.add("About", options);
		
		setLayout(new BorderLayout());
		add(waytop, BorderLayout.NORTH);
		add(tabs);
		
		updateCurrent(null);
		setSize(600,450);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setVisible(true);

		try{ p.load(); }catch(Exception e){}
		
		scanPlaylists();
	}
	
	private void scanPlaylists()
	{
		File dir = new File(System.getProperty("user.home"));
		File[] fs = dir.listFiles();

		for(File f:fs)
		{
			String x = f.getName();
			if(x.equals("Current.m3u")) continue;

			if( x.toLowerCase().endsWith(".m3u") )
			{
				String name = x.substring(0,x.length()-4);

				try
				{
					Playlist pl = new Playlist(name, this);
					pl.load();
					playlists.add(pl);
				}
				catch(Exception e)
				{
				}
			}
		}
	}
	
	public void updateCurrent(Audio a)
	{
		if(a==null)
		{
			title.setText("Add music to your playlist.");
			artist.setText("");
			album.setText("http://truemusic.servequake.com/");
			
			npTitle.setText("");
			npArtist.setText("");
			npAlbum.setText("");
			npTrack.setText("");
			npGenre.setText("");
			npFormat.setText("");

			play.setEnabled(false);
			pause.setEnabled(false);
			stop.setEnabled(false);
			next.setEnabled(false);
			previous.setEnabled(false);
			seek.setEnabled(false);
		}
		else
		{
			title.setText(a.getTitle());
			artist.setText(a.getArtist());
			album.setText(a.getAlbum());

			npTitle.setText(a.getTitle());
			npArtist.setText(a.getArtist());
			npAlbum.setText(a.getAlbum());
			npTrack.setText(a.getTrack());
			npGenre.setText(a.getGenre());
			npFormat.setText(a.formatName());
			
			seek.setMaximum(a.getFrames());

			play.setEnabled(true);
			pause.setEnabled(true);
			stop.setEnabled(true);
			next.setEnabled(true);
			previous.setEnabled(true);
			seek.setEnabled(false);
			if(a instanceof SeekableAudio) seek.setEnabled(true);

			t.setRowSelectionInterval(p.currentIndex(),p.currentIndex()); //!
			list.setSelectedValue(a,true);
		}
	}

	public void frameCompleted(int frame)
	{
		seek.setValue(frame);
	}

	public void playlistChanged()
	{
		try
		{
			p.save();
		}
		catch(Exception e)
		{}	
	}

	public void drop(DropTargetDropEvent e)
	{
		e.acceptDrop(1);
		Transferable t = e.getTransferable();
		
		try
		{
			final java.util.List<File> fl = (java.util.List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);

			new Thread()
			{
				public void run()
				{
					for(File f: fl) decide(f);
					playlistChanged();
				}
			}.start();
		}
		catch(Exception ex)
		{}
	}

	public void itemStateChanged(final ItemEvent e)
	{
		new Thread()
		{
			public void run()
			{
				if(e.getStateChange()==e.SELECTED)
				{
					Object o = playlistsbox.getSelectedItem();
				System.out.println(o);
					if(o==p) return;
					
					if(o.equals( "[Create new playlist]" ))
					{
						String name = JOptionPane.showInputDialog(parent, "Name new playlist:" );
						if(name==null||name.equals("")||name.equals("Current")||playlists.contains(name)) //TODO: contains is not working
						{	
							playlistsbox.setSelectedItem(p);
							return;
						}
						
						Playlist pl = new Playlist( name, parent);
						try{ pl.save(); }catch(Exception e){}
						playlists.add(pl);
						playlistsbox.setSelectedItem(pl);
						return;
					}
					if(o.equals( "[Remove current playlist]" ))
					{
						if(p.name.equals("Current")) //Can't remove Current
						{
							playlistsbox.setSelectedItem(p);
							return;
						}
						
						//TODO: say name of playlist being deleted
						int op = JOptionPane.showConfirmDialog(parent, "Are you sure you want to remove the current playlist?",  "Confirm delete", JOptionPane.YES_NO_OPTION,JOptionPane.QUESTION_MESSAGE);

						if(op==JOptionPane.YES_OPTION)
						{
							p.stop();
							
							p.delete();
							playlistsbox.removeItem(p);
							
							playlistsbox.setSelectedIndex(0);
						}
						return;
					}
					
					p.stop();
					p = (Playlist) o;
					t.setModel(p); //!
					list.setModel(p);
					p.setVolume(volume.getValue());
					updateCurrent(p.current);
				}
			}
		}.start();
	}

	public void keyPressed(KeyEvent e) 
	{
		if(e.getKeyCode()==KeyEvent.VK_DELETE)
		{
			if(e.getSource()==t) p.removeTrack(t.getSelectedRow()); //!
			if(e.getSource()==list) p.removeTrack(list.getSelectedIndex());
		}
	}

	public void mouseClicked(MouseEvent e)
	{
		if(e.getSource()==t)
		{
			if(e.getClickCount()==2) //double click: play selected
			{
				p.setTrack( t.rowAtPoint(e.getPoint()) );
			}
			else if(e.getButton()==MouseEvent.BUTTON3) //right click: show menu
			{
				int index = t.rowAtPoint(e.getPoint());
				if(index==-1) return;

				t.setRowSelectionInterval(index,index);
				
				new TrackMenu( p.getTrack(index) ).show(t, e.getX(), e.getY() );
			}
		}
		else if(e.getSource()==list)
		{
			if(e.getClickCount()==2) //double click: play selected
			{
				p.setTrack( list.locationToIndex(e.getPoint()) );
			}
			else if(e.getButton()==MouseEvent.BUTTON3) //right click: show menu
			{
				int index = list.locationToIndex(e.getPoint());
				if(index==-1) return;

				list.setSelectedIndex(index);

				new TrackMenu( (Audio)list.getSelectedValue() ).show(list, e.getX(), e.getY() );
			}
		}
	}

	class TrackMenu extends JPopupMenu implements ActionListener
	{
		JMenuItem play,edit,remove;
		Audio a;

		TrackMenu(Audio a)
		{
			this.a = a;

			play = add( "Play" );
			edit = add( "Tag" );
			remove = add( "Remove" );

			play.addActionListener(this);
			edit.addActionListener(this);
			remove.addActionListener(this);
		}

		public void actionPerformed(ActionEvent e)
		{
			if(e.getSource()==play) p.setTrack(a);
			else if(e.getSource()==edit) new TagDialog(parent,a);
			else if(e.getSource()==remove) p.removeTrack(a);
		}
	}//TrackMenu

	public void actionPerformed(ActionEvent e)
	{
		if(e.getSource()==play) p.play();
		else if(e.getSource()==pause) p.pause();
		else if(e.getSource()==stop) p.stop();
		else if(e.getSource()==next) p.next();
		else if(e.getSource()==previous) p.previous();
		else if(e.getSource()==add)
		{
			final JFileChooser chooser = new JFileChooser();
			
			ExampleFileFilter filter = new ExampleFileFilter("mp3", "Supported music files" );
			filter.addExtension("ogg");
			filter.addExtension("wav");
			filter.addExtension("aif");
			filter.addExtension("aiff");
			filter.addExtension("au");
			filter.addExtension("m3u");
			filter.addExtension("flac");
			filter.addExtension("spx");
			

			chooser.setDragEnabled(true);
			chooser.setFileFilter(filter);
			chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
			chooser.setMultiSelectionEnabled(true);

			if(chooser.showDialog(this, "Add to Playlist" ) == JFileChooser.APPROVE_OPTION)
			{
				new Thread()
				{
					public void run()
					{
	
						File[] fs = chooser.getSelectedFiles();

						for(File f: fs) decide(f);
						playlistChanged();
					}
				}.start();
	    	}
		}
		else if(e.getSource()==random) p.setRandom(random.isSelected());
		else if(e.getSource()==repeat) p.setRepeat(repeat.isSelected());
	}

	public void stateChanged(ChangeEvent e) 
	{
		if(e.getSource()==seek) p.seek(seek.getValue());
		else if(e.getSource()==volume) p.setVolume(volume.getValue());
	}

	private void decide(File f)
	{
		if(f.isFile()) try{ p.addTrack(f.toString()); }catch(Exception e){ e.printStackTrace(); }
		else
		{
			for(File f2: f.listFiles() )
			{
				decide(f2);
			}
		}
	}
	
	public void keyTyped(KeyEvent e){}
	public void keyReleased(KeyEvent e){}
	public void mouseEntered(MouseEvent e){}
	public void mouseExited(MouseEvent e){}
	public void mousePressed(MouseEvent e){}
	public void mouseReleased(MouseEvent e){}
	public void dragEnter(DropTargetDragEvent e){}
	public void dragExit(DropTargetEvent e){}
	public void dragOver(DropTargetDragEvent e){}
	public void dropActionChanged(DropTargetDragEvent e){}

	public static void main(String args[]) throws Exception
	{
		JFrame.setDefaultLookAndFeelDecorated(true);

		//thanks to Robert Lillack for more comprehensive solution:
		UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
		try{
			UIManager.put("AuditoryCues.playList",
			UIManager.get("AuditoryCues.allAuditoryCues"));
		}catch(Throwable e){}

		new TrueMusic();
	}
}
